using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using uk.co.tonyditchfield.tort.attributes;
using System.Reflection;
using System.Threading;

namespace uk.co.tonyditchfield.tort.persistence
{
    public sealed class DBTransactionScope : IDBTransactionScope, IDisposable
    {

        private database.language.IDBSyntax dbSyntax;
        private database.providers.DbDetails dbDetails = null;
        private IDbCommand command = null;
        private IDbTransaction transaction = null;
        private IsolationLevel isolationLevel = IsolationLevel.ReadCommitted;
        private bool enlistInTransaction = true;

        /// <summary>
        /// Default Contructor. Allows only 1 connection to the database to be maintained.
        /// Requests a transaction, and sets read committed as the isolation level.
        /// </summary>
        public DBTransactionScope() 
        {
            Setup(true, null, IsolationLevel.ReadCommitted);
        }

        /// <summary>
        /// Allows only 1 connection to the database to be maintained.
        /// </summary>
        /// <param name="enlistInTransaction">Transaction Required?</param>
        /// <param name="isolationLevel">Modify the isolsation level if required</param>
        public DBTransactionScope(bool enlistInTransaction, IsolationLevel isolationLevel)
        {
            Setup(enlistInTransaction, null, isolationLevel);
        }

        /// <summary>
        /// Allows only 1 connection to the database to be maintained.
        /// Requests a transaction by default
        /// </summary>
        /// <param name="isolationLevel">Transaction Required?</param>
        public DBTransactionScope(IsolationLevel isolationLevel)
        {
            Setup(true, null, isolationLevel);
        }

        /// <summary>
        /// Scope for the transaction
        /// </summary>
        /// <param name="enlistInTransaction">Transaction Required?</param>
        /// <param name="dbName">The database name (from the configuration connection strings)</param>
        /// <param name="isolationLevel">Modify the isolsation level if required</param>
        public DBTransactionScope(bool enlistInTransaction, string dbName, IsolationLevel isolationLevel)
        {
            Setup(enlistInTransaction, dbName, isolationLevel);
        }

        /// <summary>
        /// Scope for the transaction. Defaults to transaction required and read committed
        /// </summary>
        /// <param name="dbName">The database name (from the configuration connection strings)</param>
        public DBTransactionScope(string dbName)
        {
            Setup(true, dbName, IsolationLevel.ReadCommitted);
        }

        /// <summary>
        /// Construct allows default (read committed in sql servers case) isolation level to be modified 
        /// This also switches on transactions by default
        /// </summary>
        /// <param name="isolationLevel">Modify the isolsation level if required</param>
        /// <param name="dbName">The database name (from the configuration connection strings)</param>
        public DBTransactionScope(string dbName, IsolationLevel isolationLevel)
        {
            Setup(true, dbName, isolationLevel);
        }

        private void SetConnectionAttributes(string dbName)
        {
            database.providers.DatabaseProvider.DBDetails.TryGetValue(dbName, out dbDetails);
            if (dbDetails.Connection.State == ConnectionState.Closed) { dbDetails.Connection.Open(); }
            if (enlistInTransaction)
            {
                if (transaction == null)
                {
                    transaction = dbDetails.Connection.BeginTransaction(isolationLevel);
                }
            }
        }

        private void SetConnectionAttributes()
        {
            IEnumerator<string> enumKeys =  database.providers.DatabaseProvider.DBDetails.Keys.GetEnumerator();
            int i = 0;
            while (enumKeys.MoveNext())
            {
                if (i == 0)
                {
                    SetConnectionAttributes(enumKeys.Current);
                }
                if (i > 0)
                {
                    Utility.Logging.Log.LogWarning("Database Connection details for: " + enumKeys.Current + " NOT set." +
                        "\r\n Consider this fair warning!!!! Defaulting to 1st connection");
                }
                i++;
            }
        }

        private void Setup(bool enlistInTransaction, string dbName, IsolationLevel isolationLevel)
        {
            Utility.Logging.Log.LogInfo("Setting up TransactionScope. Enlisted: "
                + enlistInTransaction.ToString() + " DB Name: " + dbName + " Isolation Level: " + isolationLevel);
            this.enlistInTransaction = enlistInTransaction;
            if (dbName != null)
            {
                SetConnectionAttributes(dbName);
            }
            else
            {
                SetConnectionAttributes();
            }
            this.isolationLevel = isolationLevel;
        }

        private void BuildDBObject(object obj, database.language.QueryType queryType)
        {
            command = dbDetails.Connection.CreateCommand();
            if (enlistInTransaction)
                command.Transaction = transaction;

            switch (dbDetails.DbEngine)
            {
                case database.engine.DBEngine.SqlServer2005:
                case database.engine.DBEngine.SqlServer2000:
                    dbSyntax = new database.language.SQLDBSyntax(obj, command, queryType, dbDetails);
                    break;
                case database.engine.DBEngine.Oracle9i:
                case database.engine.DBEngine.Oracle10g:
                    dbSyntax = new database.language.Oracle10Syntax(obj, command, queryType, dbDetails);
                    break;
            }
        }


        #region IDBTransactionScope Members
        /// <summary>
        /// Persists the object to the database
        /// </summary>
        /// <param name="value"></param>
        /// <returns>The object persisted</returns>
        /// <exception cref="PersistenceException"></exception>
        object IDBTransactionScope.Save(object value)
        {
            try
            {
                BuildDBObject(value, database.language.QueryType.Insert);
                return dbSyntax.GetPersistedObject();
            }
            catch (DbException dbe)
            {
                transaction.Rollback();
                throw new exception.PersistenceException(dbe.ErrorCode, dbe);
            }
            catch (exception.PersistenceException pe)
            {
                transaction.Rollback();
                throw pe;
            }
        }

        int IDBTransactionScope.Save(object value, out object identity)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        object IDBTransactionScope.Update(object value)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        int IDBTransactionScope.Update(ICollection<object> objectCollection)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        object IDBTransactionScope.Delete(object value)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        int IDBTransactionScope.Delete(ICollection<object> objectCollection)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Loads objects by primary key
        /// </summary>
        /// <param name="objectToRetrieve"></param>
        /// <param name="primeKey"></param>
        /// <returns>The object loaded</returns>
        /// <exception cref="PersistenceException"></exception>
        object IDBTransactionScope.LoadByPK(Type objectToRetrieve, object primeKey)
        {
            try
            {
                Object obj = Activator.CreateInstance(objectToRetrieve);
                foreach (PropertyInfo propertyInfo in obj.GetType().GetProperties())
                {
                    if (propertyInfo.GetCustomAttributes(typeof(attributes.PrimeKeyAttribute), false).Length == 1)
                    {
                        propertyInfo.SetValue(obj, primeKey, null);
                    }
                }
                BuildDBObject(obj, database.language.QueryType.Select);
                return dbSyntax.GetPersistedObject();
            }
            catch (DbException dbe)
            {
                transaction.Rollback();
                throw new exception.PersistenceException(dbe.ErrorCode, dbe);
            }
            catch (exception.PersistenceException pe)
            {
                transaction.Rollback();
                throw pe;
            }
        }

        object IDBTransactionScope.LoadByQuery(string query)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        ICollection<object> IDBTransactionScope.LoadCollection(string query)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void IDBTransactionScope.Rollback()
        {
            // No need to explicitly rollback since its already done at the method level
            Dispose();
        }

        void IDBTransactionScope.Complete()
        {
            transaction.Commit();
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            utility.DBUtility.CleanUp(dbDetails.Connection);
        }

        #endregion
    }
}
